#include "shaderInit.h"

#include "Utility.h"
#include "fs_containers.h"
#include "ShaderDefines.h"
#include "fs_UniformDefines.h"
#include "fs_SSFlags.h"

unsigned int strLength(const char *str) {
	unsigned int count = 0;
	char check;
	while(str[count] != '\0') {
		check = str[count];
		++count;
	}

	return count + 1;
}

bool supports41() {
	const char *result = (const char*)glGetString(GL_SHADING_LANGUAGE_VERSION);
	if(!result)
		return false;

	// Uncomment to disable 4.1 GLSL shader support
	// return false;

	return (result[0] >= '4' && result[2] >= '1');
}

void initWhiteShader(fs_shader_container &s) {
	unsigned int count = strLength(WHITE_VERT);
	char *vF = new char[count];
	strncpy(vF, WHITE_VERT, count);
	count = strLength(WHITE_FRAG);
	char *fF = new char[count];
	strncpy(fF, WHITE_FRAG, count);

	Utility::shaders_t shaders = Utility::loadShaders(vF, fF, "");

	s.shader = glCreateProgram();

	glBindAttribLocation(s.shader,quad_attributes::POSITION, "Position");
	glBindAttribLocation(s.shader,quad_attributes::TEXCOORD, "Texcoord");

	Utility::attachAndLinkProgram(s.shader,shaders);

	// Get the uniform locations
	for(int i = 0; i < U_NUM_UNIFORMS; ++i)
		s.uniforms[i] = glGetUniformLocation(s.shader,fs_uniform_names(i));

	delete fF;
	delete vF;
}


void initGShader(fs_shader_container &s, const char *vertFile, const char *fragFile) {
	unsigned int count = strLength(vertFile);
	char *vF = new char[count];
	strncpy(vF, vertFile, count);
	count = strLength(fragFile);
	char *fF = new char[count];
	strncpy(fF, fragFile, count);

	Utility::shaders_t shaders = Utility::loadShaders(vF, fF, "");

	s.shader = glCreateProgram();

	glBindAttribLocation(s.shader,quad_attributes::POSITION, "Position");
	glBindAttribLocation(s.shader,quad_attributes::TEXCOORD, "Texcoord");

	Utility::attachAndLinkProgram(s.shader,shaders);

	// Get the uniform locations
	for(int i = 0; i < U_NUM_UNIFORMS; ++i)
		s.uniforms[i] = glGetUniformLocation(s.shader,fs_uniform_names(i));

	delete fF;
	delete vF;
}

void initGBufferShader(fs_first_pass_shaders &sC, const char *vertFile, const char *fragFile) {
	unsigned int count = strLength(vertFile);
	char *vF = new char[count];
	strncpy(vF, vertFile, count);
	count = strLength(fragFile);
	char *fF = new char[count];
	strncpy(fF, fragFile, count);

	// Do the vertex-color-based shader
	Utility::shaders_t shaders = Utility::loadShaders(vF, fF, VERT_COLOR);

	sC.vtShader.shader = glCreateProgram();

	glBindAttribLocation(sC.vtShader.shader,fs_vertex_attribute::POSITION, "Position");
	glBindAttribLocation(sC.vtShader.shader,fs_vertex_attribute::NORMAL, "Normal");
	glBindAttribLocation(sC.vtShader.shader,fs_vertex_attribute::COLOR, "Color");

	glBindFragDataLocation(sC.vtShader.shader, 0, "out_Normal");
	glBindFragDataLocation(sC.vtShader.shader, 1, "out_Diffuse");
	glBindFragDataLocation(sC.vtShader.shader, 2, "out_Misc");
	glBindFragDataLocation(sC.vtShader.shader, 3, "out_Depth");

	Utility::attachAndLinkProgram(sC.vtShader.shader,shaders);

	// Get the uniform locations
	for(int i = 0; i < U_NUM_UNIFORMS; ++i)
		sC.vtShader.uniforms[i] = glGetUniformLocation(sC.vtShader.shader,fs_uniform_names(i));
	
	// Now do the texture based shader
	shaders = Utility::loadShaders(vF, fF, TX_COLOR);

	sC.txShader.shader = glCreateProgram();

	glBindAttribLocation(sC.txShader.shader,fs_vertex_attribute::POSITION, "Position");
	glBindAttribLocation(sC.txShader.shader,fs_vertex_attribute::NORMAL, "Normal");
	glBindAttribLocation(sC.txShader.shader,fs_vertex_attribute::COLOR, "Color");
	glBindAttribLocation(sC.txShader.shader,fs_vertex_attribute::TEXCOORD, "txCoord");

	glBindFragDataLocation(sC.txShader.shader, 0, "out_Normal");
	glBindFragDataLocation(sC.txShader.shader, 1, "out_Diffuse");
	glBindFragDataLocation(sC.txShader.shader, 2, "out_Misc");
	glBindFragDataLocation(sC.txShader.shader, 3, "out_Depth");

	Utility::attachAndLinkProgram(sC.txShader.shader,shaders);

	// Get the uniform locations
	for(int i = 0; i < U_NUM_UNIFORMS; ++i)
		sC.txShader.uniforms[i] = glGetUniformLocation(sC.txShader.shader,fs_uniform_names(i));

	delete vF;
	delete fF;
}

void initFirstPassShader(fs_first_pass_shaders &sC, const char *vertFile, const char *fragFile) {
	unsigned int count = strLength(vertFile);
	char *vF = new char[count];
	strncpy(vF, vertFile, count);
	count = strLength(fragFile);
	char *fF = new char[count];
	strncpy(fF, fragFile, count);

	// Do the vertex-color-based shader
	Utility::shaders_t shaders = Utility::loadShaders(vF, fF, VERT_COLOR);

	sC.vtShader.shader = glCreateProgram();

	glBindAttribLocation(sC.vtShader.shader,fs_vertex_attribute::POSITION, "Position");
	glBindAttribLocation(sC.vtShader.shader,fs_vertex_attribute::NORMAL, "Normal");
	glBindAttribLocation(sC.vtShader.shader,fs_vertex_attribute::COLOR, "Color");

	glBindFragDataLocation(sC.vtShader.shader, 0, "out_Color");

	Utility::attachAndLinkProgram(sC.vtShader.shader,shaders);

	// Get the uniform locations
	for(int i = 0; i < U_NUM_UNIFORMS; ++i)
		sC.vtShader.uniforms[i] = glGetUniformLocation(sC.vtShader.shader,fs_uniform_names(i));
	
	// Now do the texture based shader
	shaders = Utility::loadShaders(vF, fF, TX_COLOR);

	sC.txShader.shader = glCreateProgram();

	glBindAttribLocation(sC.txShader.shader,fs_vertex_attribute::POSITION, "Position");
	glBindAttribLocation(sC.txShader.shader,fs_vertex_attribute::NORMAL, "Normal");
	glBindAttribLocation(sC.txShader.shader,fs_vertex_attribute::COLOR, "Color");
	glBindAttribLocation(sC.txShader.shader,fs_vertex_attribute::TEXCOORD, "txCoord");

	glBindFragDataLocation(sC.txShader.shader, 0, "out_Color");

	Utility::attachAndLinkProgram(sC.txShader.shader,shaders);

	// Get the uniform locations
	for(int i = 0; i < U_NUM_UNIFORMS; ++i)
		sC.txShader.uniforms[i] = glGetUniformLocation(sC.txShader.shader,fs_uniform_names(i));

	delete vF;
	delete fF;
}

void initForwardAmbient(fs_first_pass_shaders &sC) {
	initFirstPassShader(sC, FORWARD_AMBIENT_VERT, FORWARD_AMBIENT_FRAG);
}

void initForwardPoint(fs_first_pass_shaders &sC) {
	initFirstPassShader(sC, FORWARD_POINT_VERT, FORWARD_POINT_FRAG);
}

void initForwardDirectional(fs_first_pass_shaders &sC) {
	initFirstPassShader(sC, FORWARD_DIRECTIONAL_VERT, FORWARD_DIRECTIONAL_FRAG);
}

void initForwardSpot(fs_first_pass_shaders &sC) {
	initFirstPassShader(sC, FORWARD_SPOT_VERT, FORWARD_SPOT_FRAG);
}

void initDeferredGBuffer(fs_first_pass_shaders &sC) {
	if(supports41())
		initGBufferShader(sC, GBUFFER_VERT_41, GBUFFER_FRAG_41);
	else
		initGBufferShader(sC, GBUFFER_VERT, GBUFFER_FRAG);
}

void initDeferredViewGBuffer(fs_shader_container &s) {
	if(supports41())
		initGShader(s, GBUFFER_DISPLAY_VERT_41, GBUFFER_DISPLAY_FRAG_41);
	else
		initGShader(s, GBUFFER_DISPLAY_VERT, GBUFFER_DISPLAY_FRAG);
}

void initDeferredAmbient(fs_shader_container &s) {
	if(supports41())
		initGShader(s, DEF_AMBIENT_VERT_41, DEF_AMBIENT_FRAG_41);
	else
		initGShader(s, DEF_AMBIENT_VERT, DEF_AMBIENT_FRAG);
}

void initDeferredDirectional(fs_shader_container &s) {
	if(supports41())
		initGShader(s, DEF_DIRECTIONAL_VERT_41, DEF_DIRECTIONAL_FRAG_41);
	else
		initGShader(s, DEF_DIRECTIONAL_VERT, DEF_DIRECTIONAL_FRAG);
}

void initDeferredPoint(fs_shader_container &s) {
	if(supports41())
		initGShader(s, DEF_POINT_VERT_41, DEF_POINT_FRAG_41);
	else
		initGShader(s, DEF_POINT_VERT, DEF_POINT_FRAG);
}

void initDeferredSpot(fs_shader_container &s) {
	if(supports41())
		initGShader(s, DEF_SPOT_VERT_41, DEF_SPOT_FRAG_41);
	else
		initGShader(s, DEF_SPOT_VERT, DEF_SPOT_FRAG);
}

void setFirstPassViewAndProjection(fs_first_pass_shaders &shader, mat4 &view, mat4 &persp) {
	glUseProgram(shader.txShader.shader);
	glUniformMatrix4fv(shader.txShader.uniforms[U_VIEW],1,GL_FALSE,&view[0][0]);
	glUniformMatrix4fv(shader.txShader.uniforms[U_PERSP],1,GL_FALSE,&persp[0][0]);
	glUseProgram(shader.vtShader.shader);
	glUniformMatrix4fv(shader.vtShader.uniforms[U_VIEW],1,GL_FALSE,&view[0][0]);
	glUniformMatrix4fv(shader.vtShader.uniforms[U_PERSP],1,GL_FALSE,&persp[0][0]);
}

void setUpAndDownVector(fs_first_pass_draw_options settings) {
	mat3 invTrans = transpose(inverse(mat3(settings.view)));

	vec3 up = normalize(invTrans * vec3(0.0f, 0.0f, 1.0f));
	vec3 down = normalize(invTrans * vec3(.0f, 0.0f, -1.0f));

	glUseProgram(settings.sC.txShader.shader);
	glUniform3fv(settings.sC.txShader.uniforms[U_DOWN],1,&down[0]);
	glUniform3fv(settings.sC.txShader.uniforms[U_UP],1,&up[0]);
	glUseProgram(settings.sC.vtShader.shader);
	glUniform3fv(settings.sC.vtShader.uniforms[U_DOWN],1,&down[0]);
	glUniform3fv(settings.sC.vtShader.uniforms[U_UP],1,&up[0]);
}

void setUpAndDownVector(fs_shader_container shader, mat4 view) {
	mat3 invTrans = transpose(inverse(mat3(view)));
	
	vec3 up = normalize(invTrans * vec3(0.0f, 0.0f, 1.0f));
	vec3 down = normalize(invTrans * vec3(.0f, 0.0f, -1.0f));

	glUseProgram(shader.shader);
	glUniform3fv(shader.uniforms[U_DOWN],1,&down[0]);
	glUniform3fv(shader.uniforms[U_UP],1,&up[0]);
}

// Hemisphere light colors
vec3 topColor(0.83f, 0.85f, 1.0f);
vec3 bottomColor(.8f, .8f, .8f);

void setTopAndBottomColor(fs_shader_container shader) {
	glUseProgram(shader.shader);
	glUniform3fv(shader.uniforms[U_BOTTOM_COLOR], 1, &bottomColor[0]);
	glUniform3fv(shader.uniforms[U_TOP_COLOR], 1, &topColor[0]);	
}

void setTopAndBottomColor(fs_first_pass_draw_options settings) {
	glUseProgram(settings.sC.txShader.shader);
	glUniform3fv(settings.sC.txShader.uniforms[U_BOTTOM_COLOR], 1, &bottomColor[0]);
	glUniform3fv(settings.sC.txShader.uniforms[U_TOP_COLOR], 1, &topColor[0]);

	glUseProgram(settings.sC.vtShader.shader);
	glUniform3fv(settings.sC.vtShader.uniforms[U_TOP_COLOR], 1, &topColor[0]);
	glUniform3fv(settings.sC.vtShader.uniforms[U_BOTTOM_COLOR], 1, &bottomColor[0]);
}

void initHDRTexture(fs_shader_container &hdrTextureShader) {
	unsigned int count = strLength(HDR_TEXTURE_VERT);
	char *vF = new char[count];
	strncpy(vF, HDR_TEXTURE_VERT, count);
	count = strLength(HDR_TEXTURE_FRAG);
	char *fF = new char[count];
	strncpy(fF, HDR_TEXTURE_FRAG, count);

	// Do the vertex-color-based shader
	Utility::shaders_t shaders = Utility::loadShaders(vF, fF, "");

	hdrTextureShader.shader = glCreateProgram();

	glBindAttribLocation(hdrTextureShader.shader,quad_attributes::POSITION, "Position");
	glBindAttribLocation(hdrTextureShader.shader,quad_attributes::TEXCOORD, "Texcoord");

	Utility::attachAndLinkProgram(hdrTextureShader.shader,shaders);

	// Get the uniform locations
	for(int i = 0; i < U_NUM_UNIFORMS; ++i)
		hdrTextureShader.uniforms[i] = glGetUniformLocation(hdrTextureShader.shader,fs_uniform_names(i));

	delete vF;
	delete fF;
}

void initPostProcess(fs_shader_container &s) {
	unsigned int count;
	char *vF, *fF;
	count = strLength(PP_VERT);
	vF = new char[count];
	strncpy(vF, PP_VERT, count);
	count = strLength(PP_FRAG);
	fF = new char[count];
	strncpy(fF, PP_FRAG, count);

	// Do the vertex-color-based shader
	Utility::shaders_t shaders = Utility::loadShaders(vF, fF, "");

	s.shader = glCreateProgram();

	glBindAttribLocation(s.shader,quad_attributes::POSITION, "Position");
	glBindAttribLocation(s.shader,quad_attributes::TEXCOORD, "Texcoord");

	Utility::attachAndLinkProgram(s.shader,shaders);

	// Get the uniform locations
	for(int i = 0; i < U_NUM_UNIFORMS; ++i)
		s.uniforms[i] = glGetUniformLocation(s.shader,fs_uniform_names(i));

	delete vF;
	delete fF;
}

void initPostProcessSetup(fs_shader_container &s) {
	unsigned int count;
	char *vF, *fF;
	count = strLength(PP_SETUP_VERT);
	vF = new char[count];
	strncpy(vF, PP_SETUP_VERT, count);
	count = strLength(PP_SETUP_FRAG);
	fF = new char[count];
	strncpy(fF, PP_SETUP_FRAG, count);

	// Do the vertex-color-based shader
	Utility::shaders_t shaders = Utility::loadShaders(vF, fF, "");

	s.shader = glCreateProgram();

	glBindAttribLocation(s.shader,quad_attributes::POSITION, "Position");
	glBindAttribLocation(s.shader,quad_attributes::TEXCOORD, "Texcoord");

	// Bind outputs
	glBindFragDataLocation(s.shader, 0, "out_Edge");
	glBindFragDataLocation(s.shader, 1, "out_largeHorizontalBlur");
	glBindFragDataLocation(s.shader, 2, "out_smallHorizontalBlur");

	Utility::attachAndLinkProgram(s.shader,shaders);

	// Get the uniform locations
	for(int i = 0; i < U_NUM_UNIFORMS; ++i)
		s.uniforms[i] = glGetUniformLocation(s.shader,fs_uniform_names(i));

	delete vF;
	delete fF;
}

void initCUDATexture(fs_shader_container &cudaTextureShader) {
	unsigned int count = strLength(CUDA_TEXTURE_VERT);
	char *vF = new char[count];
	strncpy(vF, CUDA_TEXTURE_VERT, count);
	count = strLength(CUDA_TEXTURE_FRAG);
	char *fF = new char[count];
	strncpy(fF, CUDA_TEXTURE_FRAG, count);

	// Do the vertex-color-based shader
	Utility::shaders_t shaders = Utility::loadShaders(vF, fF, "");

	cudaTextureShader.shader = glCreateProgram();

	glBindAttribLocation(cudaTextureShader.shader,quad_attributes::POSITION, "Position");
	glBindAttribLocation(cudaTextureShader.shader,quad_attributes::TEXCOORD, "Texcoord");

	Utility::attachAndLinkProgram(cudaTextureShader.shader,shaders);

	// Get the uniform locations
	for(int i = 0; i < U_NUM_UNIFORMS; ++i)
		cudaTextureShader.uniforms[i] = glGetUniformLocation(cudaTextureShader.shader,fs_uniform_names(i));

	delete vF;
	delete fF;
}

string ppDefines(int shaderID) {
	string defines = string("");

	if((shaderID & SS_FLAG_NOT_SKY) == SS_FLAG_NOT_SKY)
		defines.append(string("#define PP_AMBIENT_OCCLUSION\n"));

	if((shaderID & SS_FLAG_ANIMATED) == SS_FLAG_ANIMATED)
		defines.append(string("#define PP_MOTION_BLUR\n"));

	if((shaderID & SS_FLAG_EDGE) == SS_FLAG_EDGE)
		defines.append(string("#define PP_EDGE_BLUR\n"));

	if((shaderID & SS_FLAG_DEPTH) == SS_FLAG_DEPTH && (shaderID & SS_FLAG_MAX_DEPTH) == 0)
		defines.append(string("#define PP_DEPTH_OF_FIELD\n"));
	else if((shaderID & SS_FLAG_DEPTH) == SS_FLAG_DEPTH && (shaderID & SS_FLAG_MAX_DEPTH) == SS_FLAG_MAX_DEPTH)
		defines.append(string("#define PP_MAX_DEPTH\n"));
	else if((shaderID & SS_FLAG_MAX_DEPTH) == SS_FLAG_MAX_DEPTH && (shaderID & SS_FLAG_DEPTH) == 0)
		defines.append(string("#define PP_MIN_DEPTH\n"));

	return defines;
}

void initPostProcessShaders(fs_shader_container *s) {
	unsigned int count = strLength(PP_VERT);
	char *vF = new char[count];
	strncpy(vF, PP_VERT, count);
	count = strLength(PP_FRAG);
	char *fF = new char[count];
	strncpy(fF, PP_FRAG, count);

	for(int i = 0; i < 32; ++i) {

		Utility::shaders_t shaders = Utility::loadShaders(vF, fF, ppDefines(i));

		s[i].shader = glCreateProgram();

		glBindAttribLocation(s[i].shader,quad_attributes::POSITION, "Position");
		glBindAttribLocation(s[i].shader,quad_attributes::TEXCOORD, "Texcoord");

		Utility::attachAndLinkProgram(s[i].shader,shaders);

		// Get the uniform locations
		for(int j = 0; j < U_NUM_UNIFORMS; ++j)
			s[i].uniforms[j] = glGetUniformLocation(s[i].shader,fs_uniform_names(j));
	}

	delete fF;
	delete vF;
}